#region Imported Namespaces
using System;
using System.Collections;
using System.Collections.Specialized;
using System.Data;
using System.Text;
using System.Web.UI;
using System.Web.UI.WebControls;

using PHSRAG.Insight21.Context;
using PHSRAG.Insight21.InsightUtilities;
using BO = PHSRAG.Insight21.BusinessObjects;
using DataAccess = PHSRAG.Utility.DataAccess;
using ICachePolicy = PHSRAG.Insight21.Policy.ICachePolicy;
using Tracer = PHSRAG.Utility.Tracer;
#endregion

namespace PHSRAG.Insight21.SearchContext
{
	#region Class SearchCriteria
	/// <summary>
	/// The SearchCriteria class maintains name/value pairs for all search items.
	/// </summary>
	[Serializable]
	public class SearchCriteria : NameValueCollection
	{
		#region Instance Variables
		private bool	isDirty;
		private int		version = 0;
		/// <summary>Hash table to each control id/user friendly label pair.</summary>
		private Hashtable controlLabels;
		#endregion

		#region Public Types
		public enum Level
		{
			Undefined, Summary, Overview, Detail, Max
		}
		#endregion

		#region Public Properties
		/// <summary>
		/// Gets/Sets hashtable that is keyed by the control's ID
		/// and the value is the user friendly Label for the control.
		/// </summary>
		public Hashtable ControlLabels 
		{
			get{return controlLabels;}	set {controlLabels = value;} 
		}

		/// <summary>
		/// Parses the COS key value to return a comma delimited list of COS Ids
		/// </summary>
		public string COSIds 
		{
			get
			{
				System.Text.StringBuilder sb = new System.Text.StringBuilder();

				if ( this[Constants.CosKey] != null )
				{
					//COS passes back 3 parts: BusinessUnit/Name/Id.  We only want the IDs
					//in a coma delimited list.
					string[] chiefCodes = this[Constants.CosKey].Split('|');
					for (int i = 0; i < chiefCodes.Length; ++i)
					{
						string[] chiefCodeParts = chiefCodes[i].Split(':');
						if ( chiefCodeParts.Length > 1)
							sb.AppendFormat("{0} {1}",(sb.Length > 0) ? "," : "", chiefCodeParts[1]);										 
					}
				}
				return sb.ToString();
			}
		}

		public bool	Dirty	{ set { isDirty = value; } }
		public int	Version { get { return version; }	set { version = value; } }
		#endregion

		#region Constructor
		/// <summary>
		/// Construct an instance of the SearchCriteria class.
		/// </summary>
		public SearchCriteria(int domainUserID) : base()
		{
			controlLabels = new Hashtable();
			if (domainUserID != 0)
				Add(Constants.DomainUserIDKey, Convert.ToString(domainUserID));
		}
		#endregion

		#region Overrides
		/// <summary>
		/// Compare SearchCriteria based on their contents.
		/// </summary>
		/// <param name="obj">Other search criteria to compare</param>
		/// <returns>True if the two are equal; false otherwise</returns>
		public override bool Equals(object obj)
		{
			SearchCriteria otherSearchCriteria = obj as SearchCriteria;
			System.Diagnostics.Trace.Assert(otherSearchCriteria != null);

			if ((isDirty != otherSearchCriteria.isDirty) || (Count != otherSearchCriteria.Count))
				return false;

			foreach (string key in Keys)
				if ((otherSearchCriteria[key] == null) || (otherSearchCriteria[key] != this[key]))
					return false;

			return true;
		}

		/// <summary>
		/// This method is there just to keep the compiler happy owing to the derivation from the NameValueCollection.
		/// </summary>
		/// <returns>Hashcode for this object</returns>
		public override int GetHashCode()
		{
			return base.GetHashCode();
		}

		/// <summary>
		/// Return a string representation of the search criteria content. The string is a comma-separated set of pairs
		/// of name and value.
		/// </summary>
		/// <returns>String representation of the search criteria</returns>
		public override string ToString()
		{
			StringBuilder sb = new StringBuilder();
			sb.AppendFormat("Version:{0}", version);
			foreach (string key in Keys)
				sb.AppendFormat(", {0}:{1}", key, this[key]);
			return sb.ToString();
		}

		#endregion

		#region Public Methods
		/// <summary>
		/// Clone an existing SearchCriteria.
		/// </summary>
		/// <returns>Cloned SearchCriteria</returns>
		public SearchCriteria Clone()
		{
			SearchCriteria sc = new SearchCriteria(Convert.ToInt32(this["UserID"]));
			foreach (string key in Keys)
				sc[key] = this[key];

			foreach (string key in controlLabels.Keys)
				sc.controlLabels[key] = controlLabels[key];

			sc.version = version;
			return sc;
		}

		/// <summary>
		/// To String iterates through the selected search criteria and returns
		/// a string of the crieria in alphabetical order formatted by 
		/// Name: Value{separator}...
		/// For example Agreeement Status: Active; Agreement Type: Grant; etc...
		/// </summary>
		/// <param name="separator">Used to separate criteria name/value pairs.</param>
		/// <returns></returns>
		public string ToString(string separator)
		{
			StringBuilder sb = new StringBuilder();

			SortedList list = new SortedList();
			foreach (string key in Keys)
				list.Add(key, this[key]);

			foreach (string key in list.Keys)
				if (controlLabels[key] != null) 
					sb.AppendFormat("{0}{1} ", controlLabels[key], separator);

			return sb.ToString();
		}
		#endregion

		#region Internal Methods
		internal void Reset()
		{
			Clear();
			controlLabels.Clear();
		}
		#endregion
	}
	#endregion

	#region Class SearchCriteriaArgs
	/// <summary>
	/// The SearchCriteriaArgs class encapsulates the dictionary which contains the search criteria.
	/// </summary>
	public class SearchCriteriaArgs : CommandEventArgs
	{
		/// <summary>
		/// Get the search criteria.
		/// </summary>
		public SearchCriteria SearchCriteria	{ get { return (SearchCriteria)base.CommandArgument; } }

		/// <summary>
		/// Construct an instance of the SearchCriteriaArgs class.
		/// </summary>
		/// <param name="commandName">Command name of the control which raised the associated event</param>
		/// <param name="criteria">The current search criteria</param>
		public SearchCriteriaArgs(string commandName, SearchCriteria criteria) : base(commandName, criteria)
		{
		}
	}
	#endregion

	#region Public Delegates
	/// <summary>
	/// The SearchEventHandler delegate defines the signature of the method that could handle the Search event.
	/// </summary>
	public delegate void SearchEventHandler(SearchCriteriaArgs args);
	#endregion

	#region Class SearchContext
	/// <summary>
	/// The SearchContext class is the base for all search contexts. It is the single entity which manages module specific
	/// search criteria.
	/// </summary>
	public abstract class SearchContext : BO.BusinessObject
	{
		#region Constants
		private const string SearchCriteriaKey = "searchCriteria_151FACBB-1E28-4b63-A751-E5C2ABDAD65B";
		public static string CurrentLevelKey = "currentLevel_151FACBB-1E28-4b63-A751-E5C2ABDAD65B";
		public static string CurrentModuleKey = "currentModule_151FACBB-1E28-4b63-A751-E5C2ABDAD65B";
		#endregion

		#region Constructor
		/// <summary>
		/// Construct an instance with the specified cache policy.
		/// </summary>
		/// <param name="cachePolicy">Reference to the prevalent cache policy</param>
		protected SearchContext(ICachePolicy cachePolicy) : base(cachePolicy)
		{
		}
		#endregion

		#region Public Methods
        /// <summary>
        /// Clears the search criteria for the current module and removes from Cache.
        /// </summary>
        public void ClearModuleSearchCriteria()
        {
            SearchCriteriaDictionary[CurrentModule] = null;
            CachePolicy.Cache(SearchCriteriaKey, SearchCriteriaDictionary);
        }

		/// <summary>
		/// Clear the additional search criteria at the current level and current page
		/// This method supports two behaviors:
		/// 1. If the exclusion list is empty, the entire search criteria is cleared, and the version number is incremented.
		/// 2. If the exclusion list is not emtpy, then the keys in the exclusion list will NOT be removed from the criteria, AND
		/// the version number is NOT incremented.
		/// </summary>
		/// <param name="pageName">Name of the page</param>
		/// <param name="exclusionList">Set of keys to NOT clear from the criteria</param>
		public void ClearAdditionalSearchCriteria(string pageName, params string[] exclusionList)
		{
			ResetAdditionalSearchCriteria(CurrentLevel, pageName, exclusionList);
		}

		/// <summary>
		/// Clear the picker search criteria for all pages at the summary level for the specified picker type.
		/// </summary>
		/// <param name="pickerTypeKey">The key for the picker search criteria to be removed</param>
		public void ClearPickerSearchCriteria(string pickerTypeKey)
		{
			Hashtable dictionary = LevelSearchCriteriaDictionary;
			Hashtable pageDictionary = dictionary[SearchCriteria.Level.Summary] as Hashtable;
			foreach(string key in pageDictionary.Keys)
			{
				Pair pair = GetPairAtPage(pageDictionary, key);
				if (pair != null)
				{
					SearchCriteria primarySearchCriteria = pair.First as SearchCriteria;
					primarySearchCriteria.Remove(pickerTypeKey);
					primarySearchCriteria.ControlLabels.Remove(pickerTypeKey);
					primarySearchCriteria.Version = primarySearchCriteria.Version + 1;
				}
			}
		}

		/// <summary>
		/// Reset the search state.
		/// </summary>
		public virtual void Reset(string pageName)
		{
			Reset(CurrentLevel, pageName);
			DumpSearchCriteria("Reset");
		}

		/// <summary>
		/// Set additional search criteria for the specifid page at current module and current level.
		/// </summary>
		/// <param name="pageName">Name of the page</param>
		/// <param name="key">Key for the additional criteria</param>
		/// <param name="value">Value associated with the key</param>
		public void SetAdditionalSearchCriteria(string pageName, string key, string value)
		{
			SetAdditionalSearchCriteria(CurrentLevel, pageName, key, value);
		}

		/// <summary>
		/// Set additional search criteria for the specified page at current module and specified level.
		/// </summary>
		/// <param name="level">The level at which to set the criteria</param>
		/// <param name="pageName">Name of the page</param>
		/// <param name="key">Key for the additional criteria</param>
		/// <param name="value">Value associated with the key</param>
		public void SetAdditionalSearchCriteria(SearchCriteria.Level level, string pageName, string key, string value)
		{
			Pair pair = GetPairAtPageLevel(level, pageName);
            if (pair == null)
            {
                pair = new Pair();
                SetPairAtPageLevel(level, pageName, pair);
            }
			SearchCriteria additionalSearchCriteria = pair.Second as SearchCriteria;
			if (additionalSearchCriteria == null)
				pair.Second = additionalSearchCriteria = new SearchCriteria(0);

			if (additionalSearchCriteria[key] != value)
			{
				additionalSearchCriteria[key] = value;
				additionalSearchCriteria.Version = additionalSearchCriteria.Version + 1;
			}
			CurrentLevel = level;	// $$$ Does this break anything ?
			DumpSearchCriteria("SetAdditionalSearchCriteria");
		}

		/// <summary>
		/// Sets the picker search criteria for all pages at the current module at the Primary level.
		/// </summary>
		/// <param name="key">Key for the picker criteria</param>
		/// <param name="value">Value associated with the key</param>
		public void SetPickerSearchCriteria(string key, string value)
		{
			Hashtable dictionary = LevelSearchCriteriaDictionary;
			Hashtable pageDictionary = dictionary[SearchCriteria.Level.Summary] as Hashtable;
			foreach(string pageName in pageDictionary.Keys)
			{
				Pair pair = GetPairAtPageLevel(SearchCriteria.Level.Summary, pageName);
				
				//we're guaranteed to have primary search at the summary
				//because that is where the domainuserid is stored.
				SearchCriteria primarySearchCriteria = pair.First as SearchCriteria;

				if (primarySearchCriteria[key] != value)
				{
					primarySearchCriteria[key] = value;
					primarySearchCriteria.Version = primarySearchCriteria.Version + 1;
				}
			}

			
			DumpSearchCriteria("SetAdditionalSearchCriteria");
		}

		/// <summary>
		/// Set primary search criteria for the specified page at the current module and specified level.
		/// </summary>
		/// <param name="level">The level at which to set the criteria</param>
		/// <param name="pageName">Name of the current page</param>
		/// <param name="key">Key for the additional criteria</param>
		/// <param name="value">Value associated with the key</param>
		/// <param name="ufLabel">Label to be displayed in the search criteria display control</param>
		/// <param name="labelValueSeparator">Separator</param>
		/// <param name="text">Value to be displayed in the search criteria display control</param>
		public void SetPrimarySearchCriteria(SearchCriteria.Level level, string pageName, string key, string value, 
						string ufLabel, char labelValueSeparator, string text)
		{
			Pair pair = GetPairAtPageLevel(level, pageName);
            if (pair == null)
            {
                pair = new Pair();
                SetPairAtPageLevel(level, pageName, pair);
            }
			SearchCriteria primarySearchCriteria = pair.First as SearchCriteria;
			if (primarySearchCriteria == null)
				pair.First = primarySearchCriteria = new SearchCriteria(0);

			if (primarySearchCriteria[key] != value)
			{
				primarySearchCriteria[key] = value;
				primarySearchCriteria.ControlLabels[key] = String.Format("{0}{1}{2}", ufLabel, labelValueSeparator, text);

				primarySearchCriteria.Version = primarySearchCriteria.Version + 1;
			}
			CurrentLevel = level;
			DumpSearchCriteria("SetPrimarySearchCriteria");
		}

		#endregion

		#region Public Properties
		/// <summary>
		/// Get/Set the current navigation level.
		/// </summary>
		/// <remarks>
		/// Typically, navigation controls are expected to set the level as appropriate (Summary, Overview, Detail).
		/// Summary level is the default when not set explicitly.
		/// </remarks>
		public SearchCriteria.Level CurrentLevel
		{
			get
			{
				object o = CachePolicy.GetCachedObject(CurrentLevelKey);
				return (o == null) ? SearchCriteria.Level.Summary : (SearchCriteria.Level)Enum.Parse(typeof(SearchCriteria.Level), o.ToString());
			}
			set
			{
				if (CurrentLevel != value)
				{
					CachePolicy.Cache(CurrentLevelKey, value);
					Reset((SearchCriteria.Level)Enum.Parse(typeof(SearchCriteria.Level), value.ToString()) + 1, string.Empty); 
				}
			}
		}

		/// <summary>
		/// Return the current module name. The current module name cached by the Header control as part of its navigation
		/// management.
		/// </summary>
		public string CurrentModule
		{
			get
			{
				return (string)CachePolicy.GetCachedObject(CurrentModuleKey);
			}
		}

		#endregion

		#region Private Properties
		/// <summary>
		/// Gets the search criteria hashtable for the current level.
		/// </summary>
		private Hashtable PageSearchCriteriaDictionary
		{
			get
			{
				Hashtable dictionary = LevelSearchCriteriaDictionary[CurrentLevel] as Hashtable;
				if (dictionary == null)
					LevelSearchCriteriaDictionary[CurrentLevel] = dictionary = new Hashtable();
				return dictionary;
			}
		}
		/// <summary>
		/// Get the search criteria hashtable for the current module.
		/// </summary>
		private Hashtable LevelSearchCriteriaDictionary
		{
			get
			{
				Hashtable dictionary = SearchCriteriaDictionary[CurrentModule] as Hashtable;
				if (dictionary == null)
					SearchCriteriaDictionary[CurrentModule] = dictionary = new Hashtable();
				return dictionary;
			}
		}

		/// <summary>
		/// Get the hashtable of search criteria keyed by the current module.
		/// </summary>
		private Hashtable SearchCriteriaDictionary
		{
			get
			{
				Hashtable dictionary = CachePolicy.GetCachedObject(SearchCriteriaKey) as Hashtable;
				if (dictionary == null)
					CachePolicy.Cache(SearchCriteriaKey, dictionary = new Hashtable());
				return dictionary;
			}
		}
		#endregion

		#region Private Methods
		/// <summary>
		/// Dump the contents of the search criteria using the trace facility.
		/// </summary>
		/// <param name="legend">String legend</param>
		private void DumpSearchCriteria(string legend)
		{
			Hashtable levelDictionary = LevelSearchCriteriaDictionary;
			StringBuilder sb = new StringBuilder();
			if (legend != null)
				sb.AppendFormat("{0}\n", legend);

			for (SearchCriteria.Level level = SearchCriteria.Level.Summary, maxLevel = SearchCriteria.Level.Max; level < maxLevel; ++level)
			{
				Hashtable pageDictionary = levelDictionary[level] as Hashtable;
				sb.AppendFormat("\t{0} Level: ", level);
				if (pageDictionary != null)
				{
					foreach(string key in pageDictionary.Keys)
					{
						sb.AppendFormat("\t{0} Page: ", key);
						Pair pair = GetPairAtPage(pageDictionary, key);
						if (pair == null)
							sb.AppendFormat("(not set)\n");
						else
						{
							sb.AppendFormat("[{0}] ", pair.GetHashCode());

							SearchCriteria primarySearchCriteria = pair.First as SearchCriteria;
							if (primarySearchCriteria != null)
								sb.AppendFormat("Primary: {0}", primarySearchCriteria.ToString());

							SearchCriteria additionalSearchCriteria = pair.Second as SearchCriteria;
							if (additionalSearchCriteria != null)
								sb.AppendFormat(", Additional: {0}", additionalSearchCriteria.ToString());
						}
						sb.AppendFormat("\n");
					}
				}
			}

			Tracer.Trace(sb.ToString());
		}

		/// <summary>
		/// Gets the pair for the specified page.
		/// </summary>
		/// <param name="pageName">Name of the page</param>
		private Pair GetPagePair(string pageName)
		{
			TimeStampedCriteria tsCriteria = PageSearchCriteriaDictionary[pageName] as TimeStampedCriteria;
			if (tsCriteria == null)
			{
				tsCriteria = new TimeStampedCriteria(new Pair());
				PageSearchCriteriaDictionary[pageName] = tsCriteria;
			}
			else
				tsCriteria.TimeStamp = DateTime.Now;
			return tsCriteria.CriteriaPair;
		}

		/// <summary>
		/// Get the Pair for the specified page at the specified level
		/// </summary>
		/// <param name="level">The level to get the Pair for</param>
		/// <param name="pageName">Name of the page</param>
		/// <returns>Pair at the specified level (created if necessary)</returns>
		private Pair GetPairAtPageLevel(SearchCriteria.Level level, string pageName)
		{
			//return the pair by indexing with the pageName
			Hashtable pageDictionary = LevelSearchCriteriaDictionary[level] as Hashtable;
			if (pageDictionary == null)
				LevelSearchCriteriaDictionary[level] = pageDictionary = new Hashtable();
			TimeStampedCriteria tsCriteria = pageDictionary[pageName] as TimeStampedCriteria;
			if (tsCriteria == null)
				return null;
			return tsCriteria.CriteriaPair;
		}

		/// <summary>
		/// Get the active(current pair) at the specified level. 
		/// 
		/// At each level, there could be more than one pair, each corresponding to the page that the
		/// user browsed. The active pair is the pair associated with the most recent page that the user
		/// browsed. This is identified by the time stamp that gets updated whenever the user visits 
		/// a page.
		/// </summary>
		/// <param name="level">The level to get the pair for</param>
		/// <returns>Pair object</returns>
		private Pair GetCurrentPairAtLevel(SearchCriteria.Level level)
		{
			TimeStampedCriteria returnTSCriteria = null;
			Hashtable pageDictionary = LevelSearchCriteriaDictionary[level] as Hashtable;
			if (pageDictionary == null)
				return null;
			foreach(string key in pageDictionary.Keys)
			{
				TimeStampedCriteria tsCriteria = pageDictionary[key] as TimeStampedCriteria;
				if (tsCriteria != null)
				{
					if (returnTSCriteria == null)
						returnTSCriteria = tsCriteria;
					else
					{
						if (tsCriteria.TimeStamp > returnTSCriteria.TimeStamp)
							returnTSCriteria = tsCriteria;
					}
				}
			}
			if (returnTSCriteria != null)
				return returnTSCriteria.CriteriaPair;
			return null;
		}

		/// <summary>
		/// Get the pair at the specified page
		/// </summary>
		/// <param name="pageDictionary">Page level table for a module</param>
		/// <param name="pageName">Name of the page</param>
		/// <returns>Pair object</returns>
		private static Pair GetPairAtPage(Hashtable pageDictionary, string pageName)
		{
			TimeStampedCriteria tsCriteria = pageDictionary[pageName] as TimeStampedCriteria;
			if (tsCriteria == null)
				return null;
			return tsCriteria.CriteriaPair;
		}
		
		/// <summary>
		/// Sets the new pair to the specified page at the specified level.
		/// </summary>
		/// <param name="level">Navigation level</param>
		/// <param name="pageName">Name of the page</param>
		/// <param name="currentPair">New Search Criteria Pair</param>
		private void SetPairAtPageLevel(SearchCriteria.Level level, string pageName, Pair currentPair)
		{
			//return the pair by indexing with the pageName
			Hashtable pageDictionary = LevelSearchCriteriaDictionary[level] as Hashtable;
			TimeStampedCriteria tsCriteria = new TimeStampedCriteria(currentPair);
			pageDictionary[pageName] = tsCriteria;
		}

		/// <summary>
		/// Reset the search state. The reset is carried out from the current level to the deepest level. Version number of all cleared
		/// criteria is incremented.
		/// 
		/// The pair at the current page and at the current level is reset and all the pairs at all the pages from level + 1 will be reset.
		/// </summary>
		private void Reset(SearchCriteria.Level currentLevel, string pageName)
		{

			if (currentLevel == CurrentLevel)
			{
				Pair pair = GetPagePair(pageName);
				///for all other pages at the same level, synchronize the criteria
				foreach(string key in PageSearchCriteriaDictionary.Keys)
				{
					if (key != pageName)
					{
						Pair pairAtPage = GetPairAtPage(PageSearchCriteriaDictionary, key);
						RemoveCommonCriteria(pairAtPage, pair);
					}
				}
				if (pair != null)
				{
					SearchCriteria primarySearchCriteria = pair.First as SearchCriteria;
					if (primarySearchCriteria != null)
					{
						if (currentLevel == SearchCriteria.Level.Summary)
						{
							//primary searchcriteria at summary always has domainuserid so 
							//only clear it if the count is more than 1...
							if (primarySearchCriteria.Count > 1)
							{
								//if we are at the summary level we have to 
								//pull out the domainuserid so we can put it back into the criteria
								//after the reset...this avoids blasting domainuserid from the criteria.
								string domainUserId = primarySearchCriteria[Constants.DomainUserIDKey];
								primarySearchCriteria.Reset();
								primarySearchCriteria.Add(Constants.DomainUserIDKey, domainUserId);
								primarySearchCriteria.Version = primarySearchCriteria.Version + 1;
							}
						}
						else
						{
							if (primarySearchCriteria.Count > 0)
								primarySearchCriteria.Version = primarySearchCriteria.Version + 1;

							primarySearchCriteria.Reset();
						}
					}

					SearchCriteria additionalSearchCriteria = pair.Second as SearchCriteria;
					if (additionalSearchCriteria != null)
					{
						if (additionalSearchCriteria.Count > 0)
							additionalSearchCriteria.Version = additionalSearchCriteria.Version + 1;

						additionalSearchCriteria.Reset();
					}
				}
				
			}
			//if no page is specified then criteria should be cleared from the specified level.
			Hashtable levelDictionary = LevelSearchCriteriaDictionary;
			if (!String.IsNullOrEmpty(pageName))
				currentLevel = currentLevel + 1;
			if (currentLevel < SearchCriteria.Level.Max )
			{
				for (SearchCriteria.Level level = currentLevel; level < SearchCriteria.Level.Max; ++level)
				{
					Hashtable pageDictionary = levelDictionary[level] as Hashtable;
					if (pageDictionary != null)
					{
						foreach(string pageKey in pageDictionary.Keys)
						{
							Pair pair = GetPairAtPage(pageDictionary, pageKey);
							if (pair != null)
							{
								SearchCriteria primarySearchCriteria = pair.First as SearchCriteria;
								if (primarySearchCriteria != null)
								{
									if (level == SearchCriteria.Level.Summary)
									{
										//primary searchcriteria at summary always has domainuserid so 
										//only clear it if the count is more than 1...
										if (primarySearchCriteria.Count > 1)
										{
											//if we are at the summary level we have to 
											//pull out the domainuserid so we can put it back into the criteria
											//after the reset...this avoids blasting domainuserid from the criteria.
											string domainUserId = primarySearchCriteria[Constants.DomainUserIDKey];
											primarySearchCriteria.Reset();
											primarySearchCriteria.Add(Constants.DomainUserIDKey, domainUserId);
											primarySearchCriteria.Version = primarySearchCriteria.Version + 1;
										}
									}
									else
									{
										if (primarySearchCriteria.Count > 0)
											primarySearchCriteria.Version = primarySearchCriteria.Version + 1;

										primarySearchCriteria.Reset();
									}
								}

								SearchCriteria additionalSearchCriteria = pair.Second as SearchCriteria;
								if (additionalSearchCriteria != null)
								{
									if (additionalSearchCriteria.Count > 0)
										additionalSearchCriteria.Version = additionalSearchCriteria.Version + 1;

									additionalSearchCriteria.Reset();
								}
							}
						}
					}
				}
			}
		}

		/// <summary>
		/// Page Level
		/// Reset the additional search criteria for the specified level taking into account any specified exclusions. The exlusions
		/// are specified (optionally) as a set of keys which will NOT be removed from the criteria.
		/// Note that the version number does NOT change.
		/// </summary>
		/// <param name="currentLevel">Level associated with the search criteria</param>
		/// <param name="exclusionList">Set of criteria keys which should NOT be removed</param>
		private void ResetAdditionalSearchCriteria(SearchCriteria.Level currentLevel, string pageName, params string[] exclusionList)
		{
			Pair pair = GetPairAtPageLevel(currentLevel, pageName);
			if (pair != null)
			{
				SearchCriteria additionalSearchCriteria = pair.Second as SearchCriteria;
				if (additionalSearchCriteria != null)
				{
					if (exclusionList.Length == 0)
					{
						additionalSearchCriteria.Reset();
					}
					else
					{
						ArrayList exclusionKeyList = new ArrayList();
						exclusionKeyList.AddRange(exclusionList);

						ArrayList keys = new ArrayList();
						keys.AddRange(additionalSearchCriteria.Keys);

						foreach (string key in keys)
							if (!exclusionKeyList.Contains(key))
							{
								additionalSearchCriteria.Remove(key);
								additionalSearchCriteria.ControlLabels.Remove(key);
							}
					}
				}
			}
		}

		/// <summary>
		/// Remove the Common criteria from target that is present in source
		/// </summary>
		/// <param name="target">Target pair</param>
		/// <param name="source">Source pair</param>
		private static void RemoveCommonCriteria(Pair target, Pair source)
		{
			SearchCriteria targetSearchCriteria = target.First as SearchCriteria;
			SearchCriteria sourceSearchCriteria = source.First as SearchCriteria;

			if (targetSearchCriteria != null && sourceSearchCriteria != null)
			{
				ArrayList sourceKeys = new ArrayList();
				foreach (string key in sourceSearchCriteria.Keys)
					sourceKeys.Add(key);
				foreach(string key in sourceKeys)
				{
					if (key != Constants.DomainUserIDKey)
					{
						if (targetSearchCriteria[key] != null)
							targetSearchCriteria.Remove(key);
						if (targetSearchCriteria.ControlLabels[key] != null)
							targetSearchCriteria.ControlLabels.Remove(key);
					}
				}
			}

			targetSearchCriteria = target.Second as SearchCriteria;
			sourceSearchCriteria = source.Second as SearchCriteria;

			if (targetSearchCriteria != null && sourceSearchCriteria != null)
			{
				ArrayList sourceKeys = new ArrayList();
				foreach (string key in sourceSearchCriteria.Keys)
					sourceKeys.Add(key);
				foreach(string key in sourceKeys)
				{
					if (targetSearchCriteria[key] != null)
						targetSearchCriteria.Remove(key);
					if (targetSearchCriteria.ControlLabels[key] != null)
						targetSearchCriteria.ControlLabels.Remove(key);
				}
			}

		}

		/// <summary>
		/// Synchronize all the criteria present in the target with the source.
		/// No new (key,value) pairs are added. Values are updated for existing pairs.
		/// If a (key, value) pair present in target is deleted from the source, then the 
		/// pair is also deleted from the target. The deleted pairs are found by comparing the
		/// keys in the old Search Criteria and the new value being assigned.
		/// </summary>
		/// <param name="target">Target Pair object</param>
		/// <param name="source">New Criteria</param>
		/// <param name="oldValue">Old Criteria</param>
		private static void CopyCommonCriteria(Pair target, SearchCriteria source, SearchCriteria oldValue)
		{
			SearchCriteria searchCriteria = target.First as SearchCriteria;
			if (searchCriteria != null)
			{
				ArrayList targetKeys = new ArrayList();
				foreach (string key in searchCriteria.Keys)
					targetKeys.Add(key);

				foreach (string key in targetKeys)
				{
					if (source[key] != null)
						searchCriteria[key] = source[key];
				}

				targetKeys.Clear();
				foreach (string key in searchCriteria.ControlLabels.Keys)
					targetKeys.Add(key);
				foreach (string key in targetKeys)
				{
					if (source.ControlLabels[key] != null)
						searchCriteria.ControlLabels[key] = source.ControlLabels[key];
				}
			}

			//get the keys which are removed from oldValue
			ArrayList removedKeys = new ArrayList();
			foreach(string key in oldValue.Keys)
			{
				if (source[key] == null)
					removedKeys.Add(key);
			}

			//delete these keys from target if present
			searchCriteria = target.First as SearchCriteria;
			if (searchCriteria != null)
			{
				foreach(string key in removedKeys)
				{
					if (searchCriteria[key] != null)
						searchCriteria.Remove(key);
					if (searchCriteria.ControlLabels[key] != null)
						searchCriteria.ControlLabels.Remove(key);
				}
			}


//			searchCriteria = target.Second as SearchCriteria;
//			if (searchCriteria != null)
//			{
//				foreach (string key in searchCriteria.Keys)
//				{
//					if (source[key] != null)
//						searchCriteria[key] = source[key];
//				}
//
//				foreach (string key in searchCriteria.ControlLabels.Keys)
//				{
//					if (additionalSearchCriteria.ControlLabels[key] != null)
//						searchCriteria.ControlLabels[key] = additionalSearchCriteria.ControlLabels[key];
//				}
//			}

		}
		#endregion

		#region Public Methods
		/// <summary>
		/// Get the search criteria for the specified page.
		/// The search criteria includes the search criteria for all the parent pages and the current page.
		/// 
		/// <remarks>
		/// Search criteria is maintained per module per level per page. The organization is as follows:
		/// A hashtable is maintained (within the prevalent cache policy) where the key is the current module and the value is
		/// another hashtable. The key in the later hashtable is the current level, and the value is another hashtable. 
		/// The key in this hashtable is the name of page and value is TimeStampedCriteria object having a pair 
		/// object and an associated timestamp as its members. The first value in
		/// the pair is the primary search criteria, and the second value is an optional additional search criteia. This allows
		/// maintenance of "external" search criteria at any level not directly associated with any search control (as opposed
		/// to the "primary" search criterial which has a strong binding with at least one search control).
		/// When the search criteria is requested, its contents are dependent upon the current level and the page. 
		/// The search criteria is a union of all ancestor pages' search critera from the top-level (summary) to the current level.
		/// 
		/// For example, if a page at a summary level requested the search criteria, it would get the union of the primary and
		/// additional search criteria (maintained in the aforementioned pair) at the summary level for the specified page. 
		/// However, if a page at the overview level requests the search criteria, it would get the union of search criteria 
		/// for the parent page at the summary level and the current page at the overview
		/// level. Implicit in this mechanism is the fact the version number associated with the search criteria that
		/// gets returned is the maximum of all search criteria which participlate in the level-based union.
		/// </remarks>
		/// </summary>
		/// <param name="pageName">Name of the page</param>
		/// <returns>SearchCriteria object</returns>
		public SearchCriteria GetSearchCriteria(string pageName)
		{
			SearchCriteria searchCriteria = null;

			for (SearchCriteria.Level level = SearchCriteria.Level.Summary, maxLevel = CurrentLevel; level <= maxLevel; ++level)
			{
				Pair pair;
				if (level == CurrentLevel)
					pair = GetPagePair(pageName);
				else
					pair = GetCurrentPairAtLevel(level); 
				
				if (pair == null && level == CurrentLevel)
				{
					pair = new Pair();
					SetPairAtPageLevel(level, pageName, pair);
				}

				if (pair != null)
				{
					SearchCriteria primarySearchCriteria = pair.First as SearchCriteria;
					if (primarySearchCriteria == null)
					{
						int userID = (level == SearchCriteria.Level.Summary) ?
							((UserContext)CachePolicy.GetCachedObject(UserContext.CacheKey)).DomainUserId : 0;
						pair.First = primarySearchCriteria = new SearchCriteria(userID);
					}

					SearchCriteria additionalSearchCriteria = pair.Second as SearchCriteria;
					if (additionalSearchCriteria != null)
					{
						primarySearchCriteria = primarySearchCriteria.Clone();

						foreach (string key in additionalSearchCriteria.Keys)
							primarySearchCriteria[key] = additionalSearchCriteria[key];

						foreach (string key in additionalSearchCriteria.ControlLabels.Keys)
							primarySearchCriteria.ControlLabels[key] = additionalSearchCriteria.ControlLabels[key];

						primarySearchCriteria.Version = Math.Max(primarySearchCriteria.Version, additionalSearchCriteria.Version);
					}

					if (searchCriteria == null)
						searchCriteria = primarySearchCriteria.Clone();
					else
					{
						foreach (string key in primarySearchCriteria.Keys)
							searchCriteria[key] = primarySearchCriteria[key];

						foreach (string key in primarySearchCriteria.ControlLabels.Keys)
							searchCriteria.ControlLabels[key] = primarySearchCriteria.ControlLabels[key];

						searchCriteria.Version = Math.Max(searchCriteria.Version, primarySearchCriteria.Version);
					}
				}
			}

			DumpSearchCriteria(string.Format("GET SearchCriteria (Version {0})", searchCriteria.Version));
			return searchCriteria;
		}

		/// <summary>
		/// Used by the custom controls.
		/// 
		/// While getting the search criteria, loop thru all the criteria for all the pages only at the CurrentLevel and 
		/// pick the those criteria which apply to the page specified( criteria whose key is in controlCollection)
		/// </summary>
		/// <param name="pageName">Name of the page</param>
		/// <param name="controlCollection">Control collection for the current page</param>
		/// <returns></returns>
		public SearchCriteria GetSearchCriteria(string pageName, ControlCollection controlCollection)
		{
			SearchCriteria searchCriteria = null;

			StringBuilder idList = new StringBuilder("|");
			foreach (Control control in controlCollection)
			{   /*All the visible controls or disabled controls.
                                    Ex: Agreement Summary needs to have the Fund # carried over from Financial Summary but 
                                    Fund # search is not allowed on Agreement Summary.
                                    In this case the Fund # search criteria is added to the Common Search Criteria
                                    but made invisible. This criteria is a text box search criteria*/
				TextBox textBox = control as TextBox;

				if (control.Visible || (textBox != null && textBox.Enabled == false))
					idList.AppendFormat("{0}|", control.ID);
			}
			string idListString = idList.ToString();

			for (SearchCriteria.Level level = SearchCriteria.Level.Summary, maxLevel = CurrentLevel; level <= maxLevel; ++level)
			{
				Pair pair;
				if (level == CurrentLevel)
				{
					pair = GetPagePair(pageName);

					//Merge criteria from all the pages at the current level(with only those ids that are present in the controlcollection)
					foreach (string key in PageSearchCriteriaDictionary.Keys)
					{
						if (key != pageName)
						{
							Pair pairAtPage = GetPairAtPage(PageSearchCriteriaDictionary, key);
							SearchCriteria currentSearchCriteria = pair.First as SearchCriteria;
							if (currentSearchCriteria == null)
							{
								int userID = (level == SearchCriteria.Level.Summary) ?
									((UserContext)CachePolicy.GetCachedObject(UserContext.CacheKey)).DomainUserId : 0;
								pair.First = currentSearchCriteria = new SearchCriteria(userID);
							}
							SearchCriteria primarySearchCriteria = pairAtPage.First as SearchCriteria;
							if (primarySearchCriteria != null)
							{
								
								foreach (string primarySearchCriteriaKey in primarySearchCriteria.Keys)
									if (idListString.IndexOf(string.Format("|{0}|", primarySearchCriteriaKey)) >= 0)
										currentSearchCriteria[primarySearchCriteriaKey] = primarySearchCriteria[primarySearchCriteriaKey];

								foreach (string primarySearchCriteriaControlKey in primarySearchCriteria.ControlLabels.Keys)
									if (idListString.IndexOf(string.Format("|{0}|", primarySearchCriteriaControlKey)) >= 0)
										currentSearchCriteria.ControlLabels[primarySearchCriteriaControlKey] = primarySearchCriteria.ControlLabels[primarySearchCriteriaControlKey];
								if (currentSearchCriteria.Version < primarySearchCriteria.Version)
									currentSearchCriteria.Version = primarySearchCriteria.Version;
							}

//							currentSearchCriteria = pair.Second as SearchCriteria;
//							SearchCriteria additionalSearchCriteria = pair.Second as SearchCriteria;
//							if (additionalSearchCriteria != null)
//							{
//								foreach (string key in additionalSearchCriteria.Keys)
//									currentSearchCriteria[key] = additionalSearchCriteria[key];
//
//								foreach (string key in additionalSearchCriteria.ControlLabels.Keys)
//									currentSearchCriteria.ControlLabels[key] = additionalSearchCriteria.ControlLabels[key];
//							}
						}
					}
					SetPairAtPageLevel(level, pageName, pair);
				}
				else
					pair = GetCurrentPairAtLevel(level); 
				
				if (pair == null && level == CurrentLevel)
				{
					pair = new Pair();
					SetPairAtPageLevel(level, pageName, pair);
				}
				
				if (pair != null)
				{
					SearchCriteria pSearchCriteria = pair.First as SearchCriteria;
					if (pSearchCriteria == null)
					{
						int userID = (level == SearchCriteria.Level.Summary) ?
							((UserContext)CachePolicy.GetCachedObject(UserContext.CacheKey)).DomainUserId : 0;
						pair.First = pSearchCriteria = new SearchCriteria(userID);
					}

					SearchCriteria additionalSearchCriteria = pair.Second as SearchCriteria;
					if (additionalSearchCriteria != null)
					{
						pSearchCriteria = pSearchCriteria.Clone();

						foreach (string key in additionalSearchCriteria.Keys)
							pSearchCriteria[key] = additionalSearchCriteria[key];

						foreach (string key in additionalSearchCriteria.ControlLabels.Keys)
							pSearchCriteria.ControlLabels[key] = additionalSearchCriteria.ControlLabels[key];

						pSearchCriteria.Version = Math.Max(pSearchCriteria.Version, additionalSearchCriteria.Version);
					}

					if (searchCriteria == null)
						searchCriteria = pSearchCriteria.Clone();
					else
					{
						foreach (string key in pSearchCriteria.Keys)
							searchCriteria[key] = pSearchCriteria[key];

						foreach (string key in pSearchCriteria.ControlLabels.Keys)
							searchCriteria.ControlLabels[key] = pSearchCriteria.ControlLabels[key];

						searchCriteria.Version = Math.Max(searchCriteria.Version, pSearchCriteria.Version);
					}
				}
			}

			DumpSearchCriteria(string.Format("GET SearchCriteria (Version {0})", searchCriteria.Version));
			return searchCriteria;
		}

		/// <summary>
		/// Set the new search criteria to the specified page
		/// </summary>
		/// <param name="pageName">Name of the page</param>
		/// <param name="newValue">New Criteria</param>
		public void SetSearchCriteria(string pageName, SearchCriteria newValue)
		{
			SetSearchCriteria(pageName, newValue, null);
		}

		/// <summary>
		/// Set the criteria for the page specified and synchronize the criteria with
		/// the other pages at the same level.
		/// </summary>
		/// <param name="pageName">Name of the page</param>
		/// <param name="newValue">New Search Criteria being assigned</param>
		/// <param name="oldValue">Old Search Criteria (used for synchronization)</param>
		public void SetSearchCriteria(string pageName, SearchCriteria newValue, SearchCriteria oldValue)
		{
			// The specified search criteria may contain values from higher levels. Prune the criteria such that it only
			// conatins values for the current level so that criteria does not contain value from higher levels. The pruning is also
			// carried out at the primary/additional levels so that they don't contain duplicates.
			// Clear criteria at all lower levels.

			SearchCriteria.Level currentLevel = CurrentLevel;
			SearchCriteria searchCriteria = newValue;
			Tracer.Trace(string.Format("*** {0}", searchCriteria.ToString()));

			if (currentLevel > SearchCriteria.Level.Summary)
			{
				searchCriteria = searchCriteria.Clone();

				for (SearchCriteria.Level level = SearchCriteria.Level.Summary; level < currentLevel; ++level)
				{
					//Pair pair = dictionary[level] as Pair;
					Pair pair = GetCurrentPairAtLevel(level);
					if (pair != null)
					{
						SearchCriteria criteria = pair.First as SearchCriteria;
						if (criteria != null)
						{
							foreach (string key in criteria.Keys)
								searchCriteria.Remove(key);
						}
					}
				}

				SearchCriteria additionalSearchCriteria = GetPagePair(pageName).Second as SearchCriteria;
				if (additionalSearchCriteria != null)
				{
					foreach (string key in additionalSearchCriteria.Keys)
						searchCriteria.Remove(key);
				}
			}
			Tracer.Trace(string.Format("*** Pruned: {0}", searchCriteria.ToString()));

			GetPagePair(pageName).First = searchCriteria;

			///for all other pages at the same level, synchronize the criteria
			foreach(string key in PageSearchCriteriaDictionary.Keys)
			{
				if (key != pageName)
				{
					Pair pair = GetPairAtPage(PageSearchCriteriaDictionary, key);
					CopyCommonCriteria(pair, searchCriteria, oldValue);
				}
			}
			Reset(currentLevel + 1, pageName);			
			DumpSearchCriteria("SET SearchCriteria");
		}

		#endregion
	}
	#endregion
}

// --- EOF ---
